import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import lightgbm as lgb
import logging as log

# 配置日志格式（包含时间、日志级别和消息）
log.basicConfig(
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',  # 时间格式
    level=log.INFO
)

log.info("预测开始！")

# 读取数据（使用GBK编码处理中文）
df = pd.read_csv('lilylikes-all-LO2312019AXNHS.csv', low_memory=False, encoding='GBK')


def fill_missing_dates(df):
    # 确保 date_id 列是 datetime 类型
    df['date_id'] = pd.to_datetime(df['date_id'], format='%Y%m%d')

    # 生成完整日期范围
    min_date = df['date_id'].min()
    max_date = df['date_id'].max()
    all_dates = pd.date_range(min_date, max_date, freq='D')

    # 获取所有 product_code
    # product_codes = ""

    # 创建完整日期和 product_code 的组合
    full_df = pd.DataFrame(all_dates, columns=['date_id'])

    # 合并原始数据
    full_df = full_df.merge(df, on=['date_id'], how='left')

    # 填充缺失的 sale_qty 为 0
    full_df['sale_qty'] = full_df['sale_qty'].fillna(0)

    return full_df


# df = fill_missing_dates(df)

# 数据预处理函数（关键修改点）
def preprocess_data(df):
    # 转换日期格式（添加字符串转换）
    df['date_id'] = pd.to_datetime(df['date_id'], format='%Y%m%d')
    # df['first_new_date'] = pd.to_datetime(df['first_new_date'], format='%Y/%m/%d')

    fill_missing_dates(df)

    # # 记录 product_code 和 middle_class_name 的转换前后对应关系
    # product_code_mapping = df[['product_code']].drop_duplicates().copy()
    # product_code_mapping['product_code_encoded'] = product_code_mapping['product_code'].astype('category').cat.codes
    #
    # middle_class_name_mapping = df[['middle_class_name']].drop_duplicates().copy()
    # middle_class_name_mapping['middle_class_name_encoded'] = middle_class_name_mapping['middle_class_name'].astype('category').cat.codes

    # 转换分类列数据类型
    # df['product_code'] = df['product_code'].astype('category').cat.codes
    # df['middle_class_name'] = df['middle_class_name'].astype('category').cat.codes


    # 计算产品年龄
    # df['product_age'] = (df['date_id'] - df['first_new_date']).dt.days

    # 添加时间特征
    df['year'] = df['date_id'].dt.year
    df['month'] = df['date_id'].dt.month
    df['day'] = df['date_id'].dt.day
    df['day_of_week'] = df['date_id'].dt.dayofweek
    df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)

    # 生成滞后特征
    for lag in [1,7, 14]:
        df[f'lag_{lag}'] = df.groupby('date_id')['sale_qty'].shift(lag)

    # # 滑动窗口统计
    for window in [7, 14,30]:
        df[f'rolling_mean_{window}'] = df.groupby('date_id')['sale_qty'].transform(
            lambda x: x.rolling(window, min_periods=1).mean())

    # 填充缺失值
    df.fillna(0, inplace=True)

    return df

# 执行预处理
processed_df = preprocess_data(df)
log.info("预处理完成")

# 保存 product_code 和 middle_class_name 的转换前后对应关系到 Excel 文件
# product_code_mapping.to_excel('product_code_all_mapping.xlsx', index=False)
# middle_class_name_mapping.to_excel('middle_class_name_all_mapping.xlsx', index=False)

# 确保 date_id 列是 datetime 类型
processed_df['date_id'] = pd.to_datetime(processed_df['date_id'])

# 划分数据集（现在可以正常比较日期）
train = processed_df[processed_df['date_id'] <= datetime(2024, 12, 24, 23, 59, 59)]
val = processed_df[processed_df['date_id']>= datetime(2024, 12, 25,0, 0, 0)]

# 定义特征和标签
features = [ 'year', 'month', 'day',
            'day_of_week', 'is_weekend', 'lag_1', 'lag_7',
            'lag_14', 'rolling_mean_7', 'rolling_mean_14','rolling_mean_30'
             ]
target = 'sale_qty'

# 准备数据集
X_train, y_train = train[features], train[target]
X_val, y_val = val[features], val[target]

# 创建LightGBM数据集（显式指定分类特征）
# categorical_features = ['product_code', 'middle_class_name']
train_data = lgb.Dataset(X_train, y_train)
val_data = lgb.Dataset(X_val, y_val, reference=train_data)

# 定义模型参数（移除early_stopping_rounds）
params = {
    'objective': 'regression',
    'metric': 'rmse',
    'num_leaves': 63,
    'learning_rate': 0.05,
    'feature_fraction': 0.8,
    'bagging_fraction': 0.8,
    'bagging_freq': 5,
    'verbose': -1
}
log.info("开始训练")
# 训练模型（固定训练轮数为1000）
model = lgb.train(
    params,
    train_data,
    valid_sets=[train_data, val_data],
    num_boost_round=1000
)
log.info("训练完成")

# 预测函数（保持分类特征一致性）
def predict_future(model, last_known_date):
    product_codes = ['LO2312019AXNHS']
    future_dates = [last_known_date + timedelta(days=i) for i in range(1, 8)]

    future_df = pd.DataFrame()
    for code in product_codes:
        # product_data = df[df.product_code == code].iloc[-1]
        # 显式维护分类类型（关键修改）
        temp_df = pd.DataFrame({
            'date_id': future_dates,
            # 'product_code': pd.Categorical([code] * 7, categories=df['product_code'].unique()),
            # 'middle_class_name': pd.Categorical([product_data['middle_class_name']] * 7, categories=df['middle_class_name'].unique()),
            # 'first_new_date': [product_data['first_new_date']] * 7,
            'sale_qty': [0] * 7  # 添加sale_qty列并填充默认值0
        })

        future_df = pd.concat([future_df, temp_df])

    # 预处理未来数据
    future_processed = preprocess_data(future_df)

    # 强制类型对齐
    # for col in categorical_features:
    #     future_processed[col] = future_processed[col].astype(X_train[col].dtype)

    # 执行预测
    X_future = future_processed[features]
    future_processed['predicted_sales'] = model.predict(X_future)

    # 后处理：将负值替换为0
    future_processed['predicted_sales'] = future_processed['predicted_sales'].apply(lambda x: max(x, 0))

    # 格式化输出
    future_processed['date_id'] = future_processed['date_id'].dt.strftime('%Y%m%d')
    return future_processed[['date_id', 'predicted_sales']]

# 生成预测结果
product_codes = ['LO2312019AXNHS']

log.info("预测2025年第一周")
# 预测2025年第一周
predictions_2025 = predict_future(model, datetime(2024, 12, 31))

try:
    log.info("预测未来一周")
    # 预测未来一周（从最后日期+1开始）
    last_date = processed_df['date_id'].max()
    predictions_next = predict_future(model, last_date)

    # 保存结果（保持中文编码）
    predictions_2025.to_csv('2025_first_week_predictions-all.csv', index=False, encoding='GBK')
    # predictions_next.to_csv('next_week_predictions-all.csv', index=False, encoding='GBK')
except Exception as e:
    log.error(f"预测问题：{e}")
print("预测完成！结果文件已保存")
log.info("预测完成！结果文件已保存")